﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using System.Collections.Generic;

namespace Tesla.Graphics {
    /// <summary>
    /// Standard implementation of render queue. Sets up default buckets for each of the
    /// pre-defined bucket types.
    /// </summary>
    public class RenderQueue : IRenderQueue {
        private IRenderer _renderer;
        private Dictionary<int, IRenderBucket> _buckets;
        private Object _syncObject;

        /// <summary>
        /// Creates a new RenderQueue with the default buckets.
        /// </summary>
        /// <param name="renderer">Renderer to use.</param>
        public RenderQueue(IRenderer renderer) {
            _renderer = renderer;
            _buckets = new Dictionary<int, IRenderBucket>();
            _syncObject = new Object();
            SetupDefaultBuckets();
        }

        private void SetupDefaultBuckets() {
            OpaqueRenderBucketComparer opaqueComparer = new OpaqueRenderBucketComparer(_renderer);
            OrthoRenderBucketComparer orthoComparer = new OrthoRenderBucketComparer(_renderer);
            TransparentRenderBucketComparer transparentComparer = new TransparentRenderBucketComparer(_renderer);

            _buckets.Add((int) RenderBucketType.PreBucket, new RenderBucket(_renderer, opaqueComparer));
            _buckets.Add((int) RenderBucketType.Shadow, new RenderBucket(_renderer, opaqueComparer));
            _buckets.Add((int) RenderBucketType.Opaque, new RenderBucket(_renderer, opaqueComparer));
            _buckets.Add((int) RenderBucketType.Transparent, new TransparentRenderBucket(_renderer, transparentComparer));
            _buckets.Add((int) RenderBucketType.Ortho, new OrthoRenderBucket(_renderer, orthoComparer));
            _buckets.Add((int) RenderBucketType.PostBucket, new RenderBucket(_renderer, opaqueComparer));
        }

        /// <summary>
        /// Add a render bucket to the queue. Overwrites an existing bucket.
        /// </summary>
        /// <param name="type">Bucket type to add</param>
        /// <param name="bucket">Bucket to add</param>
        public void AddRenderBucket(RenderBucketType type, IRenderBucket bucket) {
            lock(_syncObject) {
                if(bucket == null) {
                    throw new ArgumentNullException("RenderBucket cannot be null");
                }
                _buckets[(int) type] = bucket;
            }
        }

        /// <summary>
        /// Gets a render bucket by its type.
        /// </summary>
        /// <param name="type">Type to get</param>
        /// <returns>Bucket, if it exists</returns>
        public IRenderBucket GetRenderBucket(RenderBucketType type) {
            lock(_syncObject) {
                IRenderBucket bucket;
                _buckets.TryGetValue((int) type, out bucket);
                return bucket;
            }
        }

        /// <summary>
        /// Render each bucket in the queue.
        /// </summary>
        public void RenderBuckets() {
            lock(_syncObject) {
                foreach(KeyValuePair<int, IRenderBucket> kv in _buckets) {
                    kv.Value.Render();
                }
            }
        }

        /// <summary>
        /// Sort each bucket.
        /// </summary>
        public void SortBuckets() {
            lock(_syncObject) {
                foreach(KeyValuePair<int, IRenderBucket> kv in _buckets) {
                    kv.Value.Sort();
                }
            }
        }

        /// <summary>
        /// Clear each bucket.
        /// </summary>
        public void ClearBuckets() {
            lock(_syncObject) {
                foreach(KeyValuePair<int, IRenderBucket> kv in _buckets) {
                    kv.Value.Clear();
                } 
            }
        }

        /// <summary>
        /// Sort, then render each bucket.
        /// </summary>
        public void SortAndRender() {
            lock(_syncObject) {
                foreach(KeyValuePair<int, IRenderBucket> kv in _buckets) {
                    IRenderBucket bucket = kv.Value;
                    bucket.Sort();
                    bucket.Render();
                } 
            }
        }

        /// <summary>
        /// Sort, render, then clear each bucket.
        /// </summary>
        public void SortRenderClear() {
            lock(_syncObject) {
                foreach(KeyValuePair<int, IRenderBucket> kv in _buckets) {
                    IRenderBucket bucket = kv.Value;
                    bucket.Sort();
                    bucket.Render();
                    bucket.Clear();
                } 
            }
        }

        /// <summary>
        /// Adds the renderable to the queue.
        /// </summary>
        /// <param name="renderable">Renderable to add</param>
        /// <returns>True if it was added</returns>
        public bool AddToQueue(IRenderable renderable) {
            lock(_syncObject) {
                RenderBucketType type = renderable.RenderBucketType;
                if(type == RenderBucketType.Skip) {
                    return false;
                }

                if(type == RenderBucketType.Default) {
                    type = RenderBucketType.Opaque;
                }

                IRenderBucket bucket = GetRenderBucket(type);
                if(bucket != null) {
                    bucket.Add(renderable);
                    return true;
                }
                return false;
            }
        }

        /// <summary>
        /// Removes the renderable from the queue.
        /// </summary>
        /// <param name="renderable">Renderable to remove</param>
        /// <returns>True if it was removed, if it was found</returns>
        public bool RemoveFromQueue(IRenderable renderable) {
            lock(_syncObject) {
                RenderBucketType type = renderable.RenderBucketType;
                if(type == RenderBucketType.Skip) {
                    return false;
                }

                if(type == RenderBucketType.Default) {
                    type = RenderBucketType.Opaque;
                }

                IRenderBucket bucket = GetRenderBucket(type);
                if(bucket != null) {
                    bucket.Remove(renderable);
                    return true;
                }
                return false;
            }
        }
    }
}
